package common.util;

import cn.hutool.core.convert.Convert;
import com.alibaba.fastjson.JSONArray;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.opencv.core.*;
import org.opencv.features2d.Features2d;
import org.opencv.features2d.FlannBasedMatcher;
import org.opencv.features2d.SIFT;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.xfeatures2d.SURF;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.stream.Collectors;

@Component
@Slf4j
public class SortUtil {


    @Value("${central.with-opencv}")
    public boolean withOpenCV;

    @Autowired
    private MinioUtil minioUtil;

    @Autowired
    private NacosUtil nacosUtil;

    public static Boolean checkWhiteBlock(Mat img) {
        Mat clone = new Mat();
        img.copyTo(clone);
        if (clone.empty()) {
            return true;
        }
        //Imgproc.GaussianBlur(clone, clone, new Size(3, 3), 0, 0);
        //Imgproc.cvtColor(clone, clone, Imgproc.COLOR_BGR2GRAY);
        //Imgproc.adaptiveThreshold(clone, clone, 255, 0, 1, 15, 10);

        int height = clone.height();
        int width = clone.width();

        Integer heightThreshold = Convert.toInt(height * 0.15);
        Integer widthThreshold = Convert.toInt(width * 0.15);

        for (int i = 0; i < height - heightThreshold; i++) {
            for (int j = 0; j < width - widthThreshold; j++) {
                Boolean whiteBlock = true;
                T:
                for (int s = j; s < j + widthThreshold; s++) {
                    for (int t = i; t < i + heightThreshold; t++) {
                        double[] doubles = clone.get(t, s);
                        if (doubles[0] < 250 || doubles[1] < 250 || doubles[2] < 250) {
                            whiteBlock = false;
                            j = s;
                            break T;
                        }
                    }
                }

                if (whiteBlock) {
                    return true;
                }
            }
        }
        return false;
    }

    public Result compareImg(String imgFile1, JSONArray box1, String imgFile2, JSONArray box2) {

        if (withOpenCV) {
            try {
                return nacosUtil.compareImg(imgFile1, box1, imgFile2, box2);
            } catch (Exception e) {
                e.printStackTrace();
                log.info("openCv提取失败,走本地流程");
            }
        }

        Result result = new Result();

        /**
         * 最小连线数量
         */
        Integer minGoodPointNum = 10;
        /**
         * 连线总数的20%为最大值
         */
        Double singlePointPlace = 0.2;

        /**
         * 最大汉明距离
         */
        Double maxDistance = 200.0;

        /**
         * 原图目标图三角函数最小比例
         */
        Double sourceTargetLine = 0.7;

        /**
         * 圈定原图目标图三角函数比例
         */
        Double sourceTargetRound = 0.8;

        /**
         * 圈定区域最小宽
         */
        Integer roundMinWidth = 20;

        /**
         * 圈定区域最小高
         */
        Integer roundMinHeight = 20;

        /**
         * 圈定区域宽高占总宽高最小比值
         */
        Double roundSource = 0.1;


        /**
         * 无效线占总线的比例阈值
         */
        Double badInBestProportion = 0.6;

        /**
         * 是否使用图片等比
         */
        Boolean useProportion = true;


        Mat img1 = Imgcodecs.imdecode(new MatOfByte(minioUtil.downloadByte(imgFile1)), Imgcodecs.IMREAD_COLOR);
        Mat img2 = Imgcodecs.imdecode(new MatOfByte(minioUtil.downloadByte(imgFile2)), Imgcodecs.IMREAD_COLOR);

        if (img1.empty() || img2.empty()) {
            return result;
        }

        result.setSourceImgInfo(new ImgInfo(img1.width(), img1.height()));
        result.setDesImgInfo(new ImgInfo(img2.width(), img2.height()));

//        if (checkWhiteBlock(img1) || checkWhiteBlock(img2)) {
//            System.out.println("检测到有白块的图片，跳过……");
//            return null;
//        }

        Mat clone1 = new Mat();
        Mat clone2 = new Mat();
        img1.copyTo(clone1);
        img2.copyTo(clone2);


        Imgproc.GaussianBlur(clone1, clone1, new Size(3, 3), 0, 0);
        Imgproc.cvtColor(clone1, clone1, Imgproc.COLOR_BGR2GRAY);

        Imgproc.GaussianBlur(clone2, clone2, new Size(3, 3), 0, 0);
        Imgproc.cvtColor(clone2, clone2, Imgproc.COLOR_BGR2GRAY);

        Core.copyMakeBorder(img1, img1, 0, 0, 0, 5, Core.BORDER_CONSTANT, new Scalar(255, 255, 255));
        Imgproc.line(img1, new Point(img1.width() - 1, 0), new Point(img1.width() - 1, img1.height()), new Scalar(255, 255, 255), 5);

        MatOfKeyPoint keypoints1 = new MatOfKeyPoint();
        MatOfKeyPoint keypoints2 = new MatOfKeyPoint();
        Mat des1 = new Mat();
        Mat des2 = new Mat();
        SIFT sift = SIFT.create();
        sift.detectAndCompute(clone1, new Mat(), keypoints1, des1);
        sift.detectAndCompute(clone2, new Mat(), keypoints2, des2);

        if (des1.empty() || des2.empty()) {
            return new Result();
        }

        MatOfDMatch md = new MatOfDMatch();
        FlannBasedMatcher matcher = FlannBasedMatcher.create();
        matcher.match(des1, des2, md);


        double maxDist = 0;
        double minDist = 1000;
        DMatch[] mats = md.toArray();
        result.setAllLineNum(mats.length);

        /**
         * 这是一个计算汉明距离阈值的算法，
         * 具体没研究过，会取出一定数量的最佳匹配线，
         * 并过滤掉了距离大于200的所有线
         */
        List<DMatch> bestMatches = new ArrayList<>();
        for (int i = 0; i < mats.length; i++) {
            double dist = mats[i].distance;
            if (dist < minDist) {
                minDist = dist;
            }
            if (dist > maxDist) {
                maxDist = dist;
            }
        }
        double threshold = 3 * minDist;
        double threshold2 = 2 * minDist;
        if (threshold2 >= maxDist) {
            threshold = minDist * 1.1;
        } else if (threshold >= maxDist) {
            threshold = threshold2 * 1.4;
        }
        if (0d == threshold) {
            threshold = 0.3 * maxDist;
        }

        for (int i = 0; i < mats.length; i++) {
            Double dist = (double) mats[i].distance;
            if (dist <= threshold && mats[i].distance < maxDistance) {
                bestMatches.add(mats[i]);
            }
        }
        result.setBestLineNum1(bestMatches.size());

        if (bestMatches.size() < minGoodPointNum) {
            return result;
        }

        /**
         * 先对图1按X轴进行排序，然后根据勾股定理判断本点与下一个本点 连点与下一个连点之间的距离，如果距离不符合就删掉本线
         */
        Set<DMatch> badPoint1 = new HashSet<>();
        List<KeyPoint> keyPoints1 = keypoints1.toList();
        List<KeyPoint> keyPoints2 = keypoints2.toList();

        List<PointDMatch> xPointList = new ArrayList<>();
        for (int i = 0; i < bestMatches.size(); i++) {
            DMatch dMatch = bestMatches.get(i);
            KeyPoint keyPoint = keyPoints1.get(bestMatches.get(i).queryIdx);
            PointDMatch xPointDMatch = new PointDMatch();
            xPointDMatch.setPoint(keyPoint.pt.x);
            xPointDMatch.setDMatch(dMatch);
            xPointList.add(xPointDMatch);
        }
        List<PointDMatch> xPointListSort = xPointList.stream().sorted(Comparator.comparing(PointDMatch::getPoint)).collect(Collectors.toList());


        Double proportion1 = Convert.toDouble(img1.width()) / (img2.width());
        Double proportion2 = Convert.toDouble(img1.height()) / (img2.height());

        Double proportion = Math.abs(proportion1 - 1) > Math.abs(proportion2 - 1) ? proportion2 : proportion1;

        for (int i = 0; i < xPointListSort.size() - 1; i++) {

            KeyPoint keyPoint1 = keyPoints1.get(xPointListSort.get(i).getDMatch().queryIdx);
            KeyPoint keyPoint2 = keyPoints1.get(xPointListSort.get(i + 1).getDMatch().queryIdx);
            Double x1 = Math.abs(keyPoint2.pt.x - keyPoint1.pt.x);
            Double y1 = Math.abs(keyPoint2.pt.y - keyPoint1.pt.y);
            double line1 = Math.sqrt(Math.pow(x1, 2) + Math.pow(y1, 2));

            KeyPoint keyPoint3 = keyPoints2.get(xPointListSort.get(i).getDMatch().trainIdx);
            KeyPoint keyPoint4 = keyPoints2.get(xPointListSort.get(i + 1).getDMatch().trainIdx);
            Double x2 = Math.abs(keyPoint4.pt.x - keyPoint3.pt.x);
            Double y2 = Math.abs(keyPoint4.pt.y - keyPoint3.pt.y);
            double line2 = Math.sqrt(Math.pow(x2, 2) + Math.pow(y2, 2));


            Double lineRate = 0.0;
            if (line1 > 0 && line2 > 0) {
                if (useProportion) {
                    line2 = line2 * proportion;
                    lineRate = line1 > line2 ? line2 / line1 : line1 / line2;
                } else {
                    lineRate = line1 > line2 ? line2 / line1 : line1 / line2;
                }
            }

            if (lineRate < sourceTargetLine) {
                badPoint1.add(xPointListSort.get(i).getDMatch());
            }
        }

        result.setBadLineNum1(badPoint1.size());

        /**
         * 无效线占总连线的比例，如果大于某个值，就认为这两张图片不像，直接跳出。
         */
        Integer bestMatchSize = bestMatches.size();

        Double badInBest1 = badPoint1.size() / Convert.toDouble(bestMatches.size());
        result.setBadInBest1(badInBest1);
        if (bestMatches.size() < minGoodPointNum) {
            return result;
        }

//        if (badInBest1 < badInBestProportion) {
//            System.out.println("无效占比过大");
//            return;
//        }

        /**
         * 在最佳匹配中删除无效线
         */
        Iterator<DMatch> iterator = bestMatches.iterator();
        while (iterator.hasNext()) {
            DMatch next = iterator.next();
            if (badPoint1.contains(next)) {
                iterator.remove();
            }
        }
        result.setBestLineNum2(bestMatches.size());

        /**
         * 过滤孤岛点，原理是现将下一根线和当前线的X，Y轴差值排序，按百分比取出某一根的距离为阈值，
         * 然后将所有连线按照X/Y轴进行排序，然后看下一条线和当前线的X轴差值和Y轴差值，
         * 如果大于阈值就删除当前连线，防止突然某根弦离大部分的线距离很远会将方框画的很大的问题。
         */
        Set<DMatch> badPoint2 = new HashSet<>();
        List<Double> differenceListX = new ArrayList<>();
        List<Double> differenceListY = new ArrayList<>();
        for (int i = 0; i < bestMatches.size() - 1; i++) {
            KeyPoint keyPoint1X = keyPoints1.get(bestMatches.get(i).queryIdx);
            KeyPoint keyPoint1Y = keyPoints1.get(bestMatches.get(i + 1).queryIdx);
            differenceListX.add(keyPoint1Y.pt.x - keyPoint1X.pt.x);
            differenceListY.add(keyPoint1Y.pt.y - keyPoint1X.pt.y);
        }

        Map<Double, DMatch> xDatchsMap = new HashMap<>();
        Map<Double, DMatch> yDatchsMap = new HashMap<>();
        for (DMatch bestMatch : bestMatches) {
            KeyPoint keyPoint = keyPoints1.get(bestMatch.queryIdx);
            xDatchsMap.put(keyPoint.pt.x, bestMatch);
            yDatchsMap.put(keyPoint.pt.y, bestMatch);
        }

        List<DMatch> xMatchsList = new ArrayList<>();
        xDatchsMap.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEachOrdered(x -> xMatchsList.add(x.getValue()));

        List<DMatch> yMatchsList = new ArrayList<>();
        yDatchsMap.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEachOrdered(x -> yMatchsList.add(x.getValue()));

        Collections.sort(differenceListX, Collections.reverseOrder());
        Collections.sort(differenceListY, Collections.reverseOrder());

        Integer idX = Convert.toInt(differenceListX.size() * singlePointPlace);
        Integer idY = Convert.toInt(differenceListY.size() * singlePointPlace);

        Double xNum = 0.0;
        Double yNum = 0.0;
        if (idX > 0) {
            xNum = differenceListX.get(idX - 1);
        }
        if (idY > 0) {
            yNum = differenceListY.get(idY - 1);
        }

        for (int i = 0; i < xMatchsList.size() - 1; i++) {
            KeyPoint keyPoint1X = keyPoints1.get(xMatchsList.get(i).queryIdx);
            KeyPoint keyPoint1Y = keyPoints1.get(xMatchsList.get(i + 1).queryIdx);
            if (keyPoint1Y.pt.x - keyPoint1X.pt.x > xNum) {
                badPoint2.add(xMatchsList.get(i));
            }
        }

        for (int i = 0; i < yMatchsList.size() - 1; i++) {
            KeyPoint keyPoint1X = keyPoints1.get(yMatchsList.get(i).queryIdx);
            KeyPoint keyPoint1Y = keyPoints1.get(yMatchsList.get(i + 1).queryIdx);
            if (keyPoint1Y.pt.y - keyPoint1X.pt.y > yNum) {
                badPoint2.add(yMatchsList.get(i));
            }
        }
        result.setBadLineNum2(badPoint2.size());
        Double badInBest2 = badPoint2.size() / Convert.toDouble(bestMatchSize);
        result.setBadInBest2(badInBest2);

        if (bestMatches.size() < minGoodPointNum) {
            return result;
        }

        /**
         * 在孤岛坏线中删除无效线
         */
        iterator = bestMatches.iterator();
        while (iterator.hasNext()) {
            DMatch next = iterator.next();
            if (badPoint2.contains(next)) {
                iterator.remove();
            }
        }

        /**
         * 判断连线书必须大于设置的数量，并计算两图的左上角坐标和右下角坐标。
         */
        //if (bestMatches.size() > minGoodPointNum) {
        Integer minX1 = 999999999;
        Integer minY1 = 999999999;
        Integer maxX1 = 0;
        Integer maxY1 = 0;

        Integer minX2 = 999999999;
        Integer minY2 = 999999999;
        Integer maxX2 = 0;
        Integer maxY2 = 0;

        for (DMatch bestMatch : bestMatches) {
            KeyPoint keyPoint1 = keyPoints1.get(bestMatch.queryIdx);
            if (keyPoint1.pt.x < minX1) {
                minX1 = Convert.toInt(keyPoint1.pt.x);
            }

            if (keyPoint1.pt.y < minY1) {
                minY1 = Convert.toInt(keyPoint1.pt.y);
            }

            if (keyPoint1.pt.x > maxX1) {
                maxX1 = Convert.toInt(keyPoint1.pt.x);
            }

            if (keyPoint1.pt.y > maxY1) {
                maxY1 = Convert.toInt(keyPoint1.pt.y);
            }


            KeyPoint keyPoint2 = keyPoints2.get(bestMatch.trainIdx);
            if (keyPoint2.pt.x < minX2) {
                minX2 = Convert.toInt(keyPoint2.pt.x);
            }

            if (keyPoint2.pt.y < minY2) {
                minY2 = Convert.toInt(keyPoint2.pt.y);
            }

            if (keyPoint2.pt.x > maxX2) {
                maxX2 = Convert.toInt(keyPoint2.pt.x);
            }

            if (keyPoint2.pt.y > maxY2) {
                maxY2 = Convert.toInt(keyPoint2.pt.y);
            }
        }

        result.setSourceRoundPint(new RoundPoint(minX1, maxX1, minY1, maxY1));
        result.setDesRoundPint(new RoundPoint(minX2, maxX2, minY2, maxY2));


        /**
         * 计算原图和目标图圈定区域的宽和高
         */
        Integer x1 = maxX1 - minX1;
        Integer y1 = maxY1 - minY1;
        Integer x2 = maxX2 - minX2;
        Integer y2 = maxY2 - minY2;

        /**
         * 计算图1和图2画的方框占原图的比例，比例必须大于阈值才保留，防止圈了很小的框
         */
        int width1 = img1.width();
        if (width1 == 0) {
            width1 = 1;
        }
        int height1 = img1.height();
        if (height1 == 0) {
            height1 = 1;
        }
        int width2 = img2.width();
        if (width2 == 0) {
            width2 = 1;
        }
        int height2 = img2.height();
        if (height2 == 0) {
            height2 = 1;
        }
        Double img1X = Convert.toDouble((maxX1 - minX1)) / width1;
        Double img1Y = Convert.toDouble((maxY1 - minY1)) / height1;
        Double img2X = Convert.toDouble((maxX2 - minX2)) / width2;
        Double img2Y = Convert.toDouble((maxY2 - minY2)) / height2;

        result.setSourceRoundImgInfo(new RoundImgInfo(img1X, img1Y));
        result.setDesRoundImgInfo(new RoundImgInfo(img2X, img2Y));
        /**
         * 分别计算两张图片矩形框的对角线长度，最短比最长的 必须大于某个阈值，目的是防止两边圈的矩形框大小相差过大
         */
        Double line1_1 = Math.sqrt(Math.pow((maxX1 - minX1), 2) + Math.pow((maxY1 - minY1), 2));
        Double line2_1 = Math.sqrt(Math.pow((maxX2 - minX2), 2) + Math.pow((maxY2 - minY2), 2));
        Double lineRoundRate1 = line1_1 > line2_1 ? line2_1 / line1_1 : line1_1 / line2_1;

        Double img1_2_Height = Convert.toDouble(img1.height()) / img2.height();
        Double img1_2_Width = Convert.toDouble(img1.width()) / img2.width();

        Double line1_2 = Math.sqrt(Math.pow(((maxX1 - minX1) / img1_2_Height), 2) + Math.pow((maxY1 - minY1) / img1_2_Width, 2));
        Double line2_2 = Math.sqrt(Math.pow((maxX2 - minX2), 2) + Math.pow((maxY2 - minY2), 2));
        Double lineRoundRate2 = line1_2 > line2_2 ? line2_2 / line1_2 : line1_2 / line2_2;


        result.setSourceRoundLine(line1_2);
        result.setDesRoundLine(line2_2);
        result.setLineRoundRate(lineRoundRate2);

        /**
         * 需要满足的条件为
         * 1、连线数量必须大于阈值
         * 2、通过过滤原图/目标图相邻线之间对角线距离比值计算出的坏线数量占总线数量比例必须大于阈值
         * 3、通过孤岛算法过滤的坏线占总线的比例必须大于阈值
         * 4、原图和目标图圈定区域的宽和高必须大于阈值
         * 5、圈定区域的宽和高比原图的宽和高的值必须大于阈值
         * 6、两图的圈定区域对角线比值必须大于阈值
         */

        result.setBestLineNumOk(bestMatches.size() > minGoodPointNum);
        result.setBad1InBestOk(badInBest1 < badInBestProportion);
        result.setBad2InBestOk(badInBest2 < badInBestProportion);
        result.setRoundWidthAndHeightLengthOk(x1 > roundMinWidth && x2 > roundMinWidth && y1 > roundMinHeight && y2 > roundMinHeight);
        result.setRoundWidthAndHeightRatioOk(img1X > roundSource && img1Y > roundSource && img2X > roundSource && img2Y > roundSource);
        result.setRoundDiagonalRatioOk(lineRoundRate1 > sourceTargetRound || lineRoundRate2 > sourceTargetRound);


        if (bestMatches.size() > minGoodPointNum &&
                badInBest1 < badInBestProportion &&
                badInBest2 < badInBestProportion &&
                x1 > roundMinWidth && x2 > roundMinWidth && y1 > roundMinHeight && y2 > roundMinHeight &&
                img1X > roundSource && img1Y > roundSource && img2X > roundSource && img2Y > roundSource &&
                (lineRoundRate1 > sourceTargetRound || lineRoundRate2 > sourceTargetRound)) {

            result.setAllResult(true);

            Imgproc.line(img1, new Point(minX1, minY1), new Point(minX1, maxY1), new Scalar(0, 0, 255), 2);
            Imgproc.line(img1, new Point(minX1, minY1), new Point(maxX1, minY1), new Scalar(0, 0, 255), 2);
            Imgproc.line(img1, new Point(minX1, maxY1), new Point(maxX1, maxY1), new Scalar(0, 0, 255), 2);
            Imgproc.line(img1, new Point(maxX1, minY1), new Point(maxX1, maxY1), new Scalar(0, 0, 255), 2);

            Imgproc.line(img2, new Point(minX2, minY2), new Point(minX2, maxY2), new Scalar(0, 0, 255), 2);
            Imgproc.line(img2, new Point(minX2, minY2), new Point(maxX2, minY2), new Scalar(0, 0, 255), 2);
            Imgproc.line(img2, new Point(minX2, maxY2), new Point(maxX2, maxY2), new Scalar(0, 0, 255), 2);
            Imgproc.line(img2, new Point(maxX2, minY2), new Point(maxX2, maxY2), new Scalar(0, 0, 255), 2);

            Mat resultMat1 = new Mat();
            md.fromList(bestMatches);
            Features2d.drawMatches(img1, keypoints1, img2, keypoints2, md, resultMat1, Scalar.all(-1), Scalar.all(-1), new MatOfByte(), 2);

            Mat resultMat2 = new Mat();
            Features2d.drawMatches(img1, keypoints1, img2, keypoints2, new MatOfDMatch(), resultMat2, Scalar.all(-1), Scalar.all(-1), new MatOfByte(), 2);

//            HighGui.imshow("Good Matches & Object detection", resultMat1);
//                HighGui.waitKey(0);


            MatOfByte matOfByte1 = new MatOfByte();
            Imgcodecs.imencode(".jpg", resultMat1, matOfByte1);

            MatOfByte matOfByte2 = new MatOfByte();
            Imgcodecs.imencode(".jpg", resultMat2, matOfByte2);

            result.setResultImg1(matOfByte1.toArray());
            result.setResultImg2(matOfByte2.toArray());
//
//
//            if (showResultImgForTest) {
//                HighGui.imshow("Good Matches & Object detection", resultMat);
//                HighGui.waitKey(0);
//            }
        } else {
            return compareImg2(imgFile1, imgFile2);
        }

        return result;
    }

    public Result compareImg2(String imgFile1, String imgFile2) {

        Result result = new Result();

        /**
         * 最小连线数量
         */
        Integer minGoodPointNum = 10;
        /**
         * 连线总数的20%为最大值
         */
        Double singlePointPlace = 0.2;

        /**
         * 最大汉明距离
         */
        Double maxDistance = 200.0;

        /**
         * 原图目标图三角函数最小比例
         */
        Double sourceTargetLine = 0.7;

        /**
         * 圈定原图目标图三角函数比例
         */
        Double sourceTargetRound = 0.8;

        /**
         * 圈定区域最小宽
         */
        Integer roundMinWidth = 20;

        /**
         * 圈定区域最小高
         */
        Integer roundMinHeight = 20;

        /**
         * 圈定区域宽高占总宽高最小比值
         */
        Double roundSource = 0.1;


        /**
         * 无效线占总线的比例阈值
         */
        Double badInBestProportion = 0.6;

        /**
         * 是否使用图片等比
         */
        Boolean useProportion = true;


        Mat img1 = Imgcodecs.imdecode(new MatOfByte(minioUtil.downloadByte(imgFile1)), Imgcodecs.IMREAD_COLOR);
        Mat img2 = Imgcodecs.imdecode(new MatOfByte(minioUtil.downloadByte(imgFile2)), Imgcodecs.IMREAD_COLOR);

        if (img1.empty() || img2.empty()) {
            return result;
        }

        result.setSourceImgInfo(new ImgInfo(img1.width(), img1.height()));
        result.setDesImgInfo(new ImgInfo(img2.width(), img2.height()));

//        if (checkWhiteBlock(img1) || checkWhiteBlock(img2)) {
//            System.out.println("检测到有白块的图片，跳过……");
//            return null;
//        }

        Mat clone1 = new Mat();
        Mat clone2 = new Mat();
        img1.copyTo(clone1);
        img2.copyTo(clone2);


        Imgproc.GaussianBlur(clone1, clone1, new Size(3, 3), 0, 0);
        Imgproc.cvtColor(clone1, clone1, Imgproc.COLOR_BGR2GRAY);

        Imgproc.GaussianBlur(clone2, clone2, new Size(3, 3), 0, 0);
        Imgproc.cvtColor(clone2, clone2, Imgproc.COLOR_BGR2GRAY);

        Core.copyMakeBorder(img1, img1, 0, 0, 0, 5, Core.BORDER_CONSTANT, new Scalar(255, 255, 255));
        Imgproc.line(img1, new Point(img1.width() - 1, 0), new Point(img1.width() - 1, img1.height()), new Scalar(255, 255, 255), 5);

        MatOfKeyPoint keypoints1 = new MatOfKeyPoint();
        MatOfKeyPoint keypoints2 = new MatOfKeyPoint();
        Mat des1 = new Mat();
        Mat des2 = new Mat();
        SURF sift = SURF.create();
        sift.detectAndCompute(clone1, new Mat(), keypoints1, des1);
        sift.detectAndCompute(clone2, new Mat(), keypoints2, des2);

        if (des1.empty() || des2.empty()) {
            return new Result();
        }

        MatOfDMatch md = new MatOfDMatch();
        FlannBasedMatcher matcher = FlannBasedMatcher.create();
        matcher.match(des1, des2, md);


        double maxDist = 0;
        double minDist = 1000;
        DMatch[] mats = md.toArray();
        result.setAllLineNum(mats.length);

        /**
         * 这是一个计算汉明距离阈值的算法，
         * 具体没研究过，会取出一定数量的最佳匹配线，
         * 并过滤掉了距离大于200的所有线
         */
        List<DMatch> bestMatches = new ArrayList<>();
        for (int i = 0; i < mats.length; i++) {
            double dist = mats[i].distance;
            if (dist < minDist) {
                minDist = dist;
            }
            if (dist > maxDist) {
                maxDist = dist;
            }
        }
        double threshold = 3 * minDist;
        double threshold2 = 2 * minDist;
        if (threshold2 >= maxDist) {
            threshold = minDist * 1.1;
        } else if (threshold >= maxDist) {
            threshold = threshold2 * 1.4;
        }
        if (0d == threshold) {
            threshold = 0.3 * maxDist;
        }

        for (int i = 0; i < mats.length; i++) {
            Double dist = (double) mats[i].distance;
            if (dist <= threshold && mats[i].distance < maxDistance) {
                bestMatches.add(mats[i]);
            }
        }
        result.setBestLineNum1(bestMatches.size());

        if (bestMatches.size() < minGoodPointNum) {
            return result;
        }

        /**
         * 先对图1按X轴进行排序，然后根据勾股定理判断本点与下一个本点 连点与下一个连点之间的距离，如果距离不符合就删掉本线
         */
        Set<DMatch> badPoint1 = new HashSet<>();
        List<KeyPoint> keyPoints1 = keypoints1.toList();
        List<KeyPoint> keyPoints2 = keypoints2.toList();

        List<PointDMatch> xPointList = new ArrayList<>();
        for (int i = 0; i < bestMatches.size(); i++) {
            DMatch dMatch = bestMatches.get(i);
            KeyPoint keyPoint = keyPoints1.get(bestMatches.get(i).queryIdx);
            PointDMatch xPointDMatch = new PointDMatch();
            xPointDMatch.setPoint(keyPoint.pt.x);
            xPointDMatch.setDMatch(dMatch);
            xPointList.add(xPointDMatch);
        }
        List<PointDMatch> xPointListSort = xPointList.stream().sorted(Comparator.comparing(PointDMatch::getPoint)).collect(Collectors.toList());


        Double proportion1 = Convert.toDouble(img1.width()) / (img2.width());
        Double proportion2 = Convert.toDouble(img1.height()) / (img2.height());

        Double proportion = Math.abs(proportion1 - 1) > Math.abs(proportion2 - 1) ? proportion2 : proportion1;

        for (int i = 0; i < xPointListSort.size() - 1; i++) {

            KeyPoint keyPoint1 = keyPoints1.get(xPointListSort.get(i).getDMatch().queryIdx);
            KeyPoint keyPoint2 = keyPoints1.get(xPointListSort.get(i + 1).getDMatch().queryIdx);
            Double x1 = Math.abs(keyPoint2.pt.x - keyPoint1.pt.x);
            Double y1 = Math.abs(keyPoint2.pt.y - keyPoint1.pt.y);
            double line1 = Math.sqrt(Math.pow(x1, 2) + Math.pow(y1, 2));

            KeyPoint keyPoint3 = keyPoints2.get(xPointListSort.get(i).getDMatch().trainIdx);
            KeyPoint keyPoint4 = keyPoints2.get(xPointListSort.get(i + 1).getDMatch().trainIdx);
            Double x2 = Math.abs(keyPoint4.pt.x - keyPoint3.pt.x);
            Double y2 = Math.abs(keyPoint4.pt.y - keyPoint3.pt.y);
            double line2 = Math.sqrt(Math.pow(x2, 2) + Math.pow(y2, 2));


            Double lineRate = 0.0;
            if (line1 > 0 && line2 > 0) {
                if (useProportion) {
                    line2 = line2 * proportion;
                    lineRate = line1 > line2 ? line2 / line1 : line1 / line2;
                } else {
                    lineRate = line1 > line2 ? line2 / line1 : line1 / line2;
                }
            }

            if (lineRate < sourceTargetLine) {
                badPoint1.add(xPointListSort.get(i).getDMatch());
            }
        }

        result.setBadLineNum1(badPoint1.size());

        /**
         * 无效线占总连线的比例，如果大于某个值，就认为这两张图片不像，直接跳出。
         */
        Integer bestMatchSize = bestMatches.size();

        Double badInBest1 = badPoint1.size() / Convert.toDouble(bestMatches.size());
        result.setBadInBest1(badInBest1);
        if (bestMatches.size() < minGoodPointNum) {
            return result;
        }

//        if (badInBest1 < badInBestProportion) {
//            System.out.println("无效占比过大");
//            return;
//        }

        /**
         * 在最佳匹配中删除无效线
         */
        Iterator<DMatch> iterator = bestMatches.iterator();
        while (iterator.hasNext()) {
            DMatch next = iterator.next();
            if (badPoint1.contains(next)) {
                iterator.remove();
            }
        }
        result.setBestLineNum2(bestMatches.size());

        /**
         * 过滤孤岛点，原理是现将下一根线和当前线的X，Y轴差值排序，按百分比取出某一根的距离为阈值，
         * 然后将所有连线按照X/Y轴进行排序，然后看下一条线和当前线的X轴差值和Y轴差值，
         * 如果大于阈值就删除当前连线，防止突然某根弦离大部分的线距离很远会将方框画的很大的问题。
         */
        Set<DMatch> badPoint2 = new HashSet<>();
        List<Double> differenceListX = new ArrayList<>();
        List<Double> differenceListY = new ArrayList<>();
        for (int i = 0; i < bestMatches.size() - 1; i++) {
            KeyPoint keyPoint1X = keyPoints1.get(bestMatches.get(i).queryIdx);
            KeyPoint keyPoint1Y = keyPoints1.get(bestMatches.get(i + 1).queryIdx);
            differenceListX.add(keyPoint1Y.pt.x - keyPoint1X.pt.x);
            differenceListY.add(keyPoint1Y.pt.y - keyPoint1X.pt.y);
        }

        Map<Double, DMatch> xDatchsMap = new HashMap<>();
        Map<Double, DMatch> yDatchsMap = new HashMap<>();
        for (DMatch bestMatch : bestMatches) {
            KeyPoint keyPoint = keyPoints1.get(bestMatch.queryIdx);
            xDatchsMap.put(keyPoint.pt.x, bestMatch);
            yDatchsMap.put(keyPoint.pt.y, bestMatch);
        }

        List<DMatch> xMatchsList = new ArrayList<>();
        xDatchsMap.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEachOrdered(x -> xMatchsList.add(x.getValue()));

        List<DMatch> yMatchsList = new ArrayList<>();
        yDatchsMap.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEachOrdered(x -> yMatchsList.add(x.getValue()));

        Collections.sort(differenceListX, Collections.reverseOrder());
        Collections.sort(differenceListY, Collections.reverseOrder());

        Integer idX = Convert.toInt(differenceListX.size() * singlePointPlace);
        Integer idY = Convert.toInt(differenceListY.size() * singlePointPlace);

        Double xNum = 0.0;
        Double yNum = 0.0;
        if (idX > 0) {
            xNum = differenceListX.get(idX - 1);
        }
        if (idY > 0) {
            yNum = differenceListY.get(idY - 1);
        }

        for (int i = 0; i < xMatchsList.size() - 1; i++) {
            KeyPoint keyPoint1X = keyPoints1.get(xMatchsList.get(i).queryIdx);
            KeyPoint keyPoint1Y = keyPoints1.get(xMatchsList.get(i + 1).queryIdx);
            if (keyPoint1Y.pt.x - keyPoint1X.pt.x > xNum) {
                badPoint2.add(xMatchsList.get(i));
            }
        }

        for (int i = 0; i < yMatchsList.size() - 1; i++) {
            KeyPoint keyPoint1X = keyPoints1.get(yMatchsList.get(i).queryIdx);
            KeyPoint keyPoint1Y = keyPoints1.get(yMatchsList.get(i + 1).queryIdx);
            if (keyPoint1Y.pt.y - keyPoint1X.pt.y > yNum) {
                badPoint2.add(yMatchsList.get(i));
            }
        }
        result.setBadLineNum2(badPoint2.size());
        Double badInBest2 = badPoint2.size() / Convert.toDouble(bestMatchSize);
        result.setBadInBest2(badInBest2);

        if (bestMatches.size() < minGoodPointNum) {
            return result;
        }

        /**
         * 在孤岛坏线中删除无效线
         */
        iterator = bestMatches.iterator();
        while (iterator.hasNext()) {
            DMatch next = iterator.next();
            if (badPoint2.contains(next)) {
                iterator.remove();
            }
        }

        /**
         * 判断连线书必须大于设置的数量，并计算两图的左上角坐标和右下角坐标。
         */
        //if (bestMatches.size() > minGoodPointNum) {
        Integer minX1 = 999999999;
        Integer minY1 = 999999999;
        Integer maxX1 = 0;
        Integer maxY1 = 0;

        Integer minX2 = 999999999;
        Integer minY2 = 999999999;
        Integer maxX2 = 0;
        Integer maxY2 = 0;

        for (DMatch bestMatch : bestMatches) {
            KeyPoint keyPoint1 = keyPoints1.get(bestMatch.queryIdx);
            if (keyPoint1.pt.x < minX1) {
                minX1 = Convert.toInt(keyPoint1.pt.x);
            }

            if (keyPoint1.pt.y < minY1) {
                minY1 = Convert.toInt(keyPoint1.pt.y);
            }

            if (keyPoint1.pt.x > maxX1) {
                maxX1 = Convert.toInt(keyPoint1.pt.x);
            }

            if (keyPoint1.pt.y > maxY1) {
                maxY1 = Convert.toInt(keyPoint1.pt.y);
            }


            KeyPoint keyPoint2 = keyPoints2.get(bestMatch.trainIdx);
            if (keyPoint2.pt.x < minX2) {
                minX2 = Convert.toInt(keyPoint2.pt.x);
            }

            if (keyPoint2.pt.y < minY2) {
                minY2 = Convert.toInt(keyPoint2.pt.y);
            }

            if (keyPoint2.pt.x > maxX2) {
                maxX2 = Convert.toInt(keyPoint2.pt.x);
            }

            if (keyPoint2.pt.y > maxY2) {
                maxY2 = Convert.toInt(keyPoint2.pt.y);
            }
        }

        result.setSourceRoundPint(new RoundPoint(minX1, maxX1, minY1, maxY1));
        result.setDesRoundPint(new RoundPoint(minX2, maxX2, minY2, maxY2));


        /**
         * 计算原图和目标图圈定区域的宽和高
         */
        Integer x1 = maxX1 - minX1;
        Integer y1 = maxY1 - minY1;
        Integer x2 = maxX2 - minX2;
        Integer y2 = maxY2 - minY2;

        /**
         * 计算图1和图2画的方框占原图的比例，比例必须大于阈值才保留，防止圈了很小的框
         */
        int width1 = img1.width();
        if (width1 == 0) {
            width1 = 1;
        }
        int height1 = img1.height();
        if (height1 == 0) {
            height1 = 1;
        }
        int width2 = img2.width();
        if (width2 == 0) {
            width2 = 1;
        }
        int height2 = img2.height();
        if (height2 == 0) {
            height2 = 1;
        }
        Double img1X = Convert.toDouble((maxX1 - minX1)) / width1;
        Double img1Y = Convert.toDouble((maxY1 - minY1)) / height1;
        Double img2X = Convert.toDouble((maxX2 - minX2)) / width2;
        Double img2Y = Convert.toDouble((maxY2 - minY2)) / height2;

        result.setSourceRoundImgInfo(new RoundImgInfo(img1X, img1Y));
        result.setDesRoundImgInfo(new RoundImgInfo(img2X, img2Y));
        /**
         * 分别计算两张图片矩形框的对角线长度，最短比最长的 必须大于某个阈值，目的是防止两边圈的矩形框大小相差过大
         */
        Double line1_1 = Math.sqrt(Math.pow((maxX1 - minX1), 2) + Math.pow((maxY1 - minY1), 2));
        Double line2_1 = Math.sqrt(Math.pow((maxX2 - minX2), 2) + Math.pow((maxY2 - minY2), 2));
        Double lineRoundRate1 = line1_1 > line2_1 ? line2_1 / line1_1 : line1_1 / line2_1;

        Double img1_2_Height = Convert.toDouble(img1.height()) / img2.height();
        Double img1_2_Width = Convert.toDouble(img1.width()) / img2.width();

        Double line1_2 = Math.sqrt(Math.pow(((maxX1 - minX1) / img1_2_Height), 2) + Math.pow((maxY1 - minY1) / img1_2_Width, 2));
        Double line2_2 = Math.sqrt(Math.pow((maxX2 - minX2), 2) + Math.pow((maxY2 - minY2), 2));
        Double lineRoundRate2 = line1_2 > line2_2 ? line2_2 / line1_2 : line1_2 / line2_2;


        result.setSourceRoundLine(line1_2);
        result.setDesRoundLine(line2_2);
        result.setLineRoundRate(lineRoundRate2);

        /**
         * 需要满足的条件为
         * 1、连线数量必须大于阈值
         * 2、通过过滤原图/目标图相邻线之间对角线距离比值计算出的坏线数量占总线数量比例必须大于阈值
         * 3、通过孤岛算法过滤的坏线占总线的比例必须大于阈值
         * 4、原图和目标图圈定区域的宽和高必须大于阈值
         * 5、圈定区域的宽和高比原图的宽和高的值必须大于阈值
         * 6、两图的圈定区域对角线比值必须大于阈值
         */

        result.setBestLineNumOk(bestMatches.size() > minGoodPointNum);
        result.setBad1InBestOk(badInBest1 < badInBestProportion);
        result.setBad2InBestOk(badInBest2 < badInBestProportion);
        result.setRoundWidthAndHeightLengthOk(x1 > roundMinWidth && x2 > roundMinWidth && y1 > roundMinHeight && y2 > roundMinHeight);
        result.setRoundWidthAndHeightRatioOk(img1X > roundSource && img1Y > roundSource && img2X > roundSource && img2Y > roundSource);
        result.setRoundDiagonalRatioOk(lineRoundRate1 > sourceTargetRound || lineRoundRate2 > sourceTargetRound);


        if (bestMatches.size() > minGoodPointNum &&
                badInBest1 < badInBestProportion &&
                badInBest2 < badInBestProportion &&
                x1 > roundMinWidth && x2 > roundMinWidth && y1 > roundMinHeight && y2 > roundMinHeight &&
                img1X > roundSource && img1Y > roundSource && img2X > roundSource && img2Y > roundSource &&
                (lineRoundRate1 > sourceTargetRound || lineRoundRate2 > sourceTargetRound)) {

            result.setAllResult(true);

            Imgproc.line(img1, new Point(minX1, minY1), new Point(minX1, maxY1), new Scalar(0, 0, 255), 2);
            Imgproc.line(img1, new Point(minX1, minY1), new Point(maxX1, minY1), new Scalar(0, 0, 255), 2);
            Imgproc.line(img1, new Point(minX1, maxY1), new Point(maxX1, maxY1), new Scalar(0, 0, 255), 2);
            Imgproc.line(img1, new Point(maxX1, minY1), new Point(maxX1, maxY1), new Scalar(0, 0, 255), 2);

            Imgproc.line(img2, new Point(minX2, minY2), new Point(minX2, maxY2), new Scalar(0, 0, 255), 2);
            Imgproc.line(img2, new Point(minX2, minY2), new Point(maxX2, minY2), new Scalar(0, 0, 255), 2);
            Imgproc.line(img2, new Point(minX2, maxY2), new Point(maxX2, maxY2), new Scalar(0, 0, 255), 2);
            Imgproc.line(img2, new Point(maxX2, minY2), new Point(maxX2, maxY2), new Scalar(0, 0, 255), 2);

            Mat resultMat1 = new Mat();
            md.fromList(bestMatches);
            Features2d.drawMatches(img1, keypoints1, img2, keypoints2, md, resultMat1, Scalar.all(-1), Scalar.all(-1), new MatOfByte(), 2);

            Mat resultMat2 = new Mat();
            Features2d.drawMatches(img1, keypoints1, img2, keypoints2, new MatOfDMatch(), resultMat2, Scalar.all(-1), Scalar.all(-1), new MatOfByte(), 2);

//            HighGui.imshow("Good Matches & Object detection", resultMat1);
//                HighGui.waitKey(0);


            MatOfByte matOfByte1 = new MatOfByte();
            Imgcodecs.imencode(".jpg", resultMat1, matOfByte1);

            MatOfByte matOfByte2 = new MatOfByte();
            Imgcodecs.imencode(".jpg", resultMat2, matOfByte2);

            result.setResultImg1(matOfByte1.toArray());
            result.setResultImg2(matOfByte2.toArray());
//
//
//            if (showResultImgForTest) {
//                HighGui.imshow("Good Matches & Object detection", resultMat);
//                HighGui.waitKey(0);
//            }
        } else {
            result.setAllResult(false);
        }


        return result;
    }

    @Data
    public static class Result {

        /**
         * 原图宽高
         */
        private ImgInfo sourceImgInfo;

        /**
         * 目标图宽高
         */
        private ImgInfo desImgInfo;

        /**
         * 通过sift算法计算出来的所有线条数量
         */
        private Integer allLineNum = 0;

        /**
         * 通过汉明距离算法计算后剩余的连线数量
         */
        private Integer bestLineNum1 = 0;

        /**
         * 通过过滤原图/目标图相邻线之间对角线距离比值计算出的坏线数量
         */
        private Integer badLineNum1 = 0;

        /**
         * 通过过滤原图/目标图相邻线之间对角线距离比值计算出的坏线数量占总线数量比例
         */
        private Double badInBest1 = 0.0;

        /**
         * 通过过滤原图/目标图相邻线之间对角线距离比值后剩下的最佳线
         */
        private Integer bestLineNum2 = 0;

        /**
         * 通过过滤孤岛算法后得到的坏线数量
         */
        private Integer badLineNum2 = 0;

        /**
         * 通过孤岛算法过滤的坏线占总线的比例
         */
        private Double badInBest2 = 0.0;

        /**
         * 原图圈定区域坐标
         */
        private RoundPoint sourceRoundPint;

        /**
         * 目标图圈定区域坐标
         */
        private RoundPoint desRoundPint;

        /**
         * 原图圈定区域比值
         */
        private RoundImgInfo sourceRoundImgInfo;

        /**
         * 目标图圈定区域比值
         */
        private RoundImgInfo desRoundImgInfo;

        /**
         * 原图圈定区域对角线长度
         */
        private Double sourceRoundLine = 0.0;

        /**
         * 目标图圈定区域对角线长度
         */
        private Double desRoundLine = 0.0;

        /**
         * 原图/目标图对角线长度比值
         */
        private Double lineRoundRate = 0.0;

        /**
         * 是否符合最小连线数量
         */
        private Boolean bestLineNumOk = false;

        /**
         * 是否符合三角函数计算坏线占总线比
         */
        private Boolean bad1InBestOk = false;

        /**
         * 是否符合过滤孤岛
         */
        private Boolean bad2InBestOk = false;

        /**
         * 是否符合原图和目标图圈定区域的宽和高必须大于阈值
         */
        private Boolean roundWidthAndHeightLengthOk = false;

        /**
         * 是否符合圈定区域的宽和高比原图的宽和高的值必须大于阈值
         */
        private Boolean roundWidthAndHeightRatioOk = false;

        /**
         * 两图的圈定区域对角线比值必须大于阈值
         */
        private Boolean roundDiagonalRatioOk = false;

        /**
         * 最终结果
         */
        private Boolean allResult = false;

        /**
         * 带连线和圈定框的结果
         */

        private byte[] resultImg1;

        /**
         * 只圈定框的结果
         */
        private byte[] resultImg2;

        private String resultImg;

        private boolean resize;
        private List<Integer> points;
        private int hammingFilterLineNum;
        private double hammingFilterGoodinAllNum;
        private int robustFilterLineNum;
        private double robustFilterGoodinAllNum;
        private int riangularFilterLineNum;
        private double riangularFilterGoodinAllNum;
        private int isolatedFilterLineNum;
        private double isolatedFilterGoodinAllNum;
        private double box1inimg1diagonal;
        private double box2inimg1diagonal;
        private String endStep;
        private String cost;


    }

    @Data
    public static class PointDMatch {
        Double point;
        DMatch dMatch;
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class RoundPoint {
        Integer minX;
        Integer maxX;
        Integer minY;
        Integer maxY;
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class ImgInfo {
        Integer width;
        Integer height;
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class RoundImgInfo {
        Double widthRatio;
        Double heightRatio;
    }
}
